dash_charts.coordinate_chart⚓︎
Coordinate chart.
Creates a grid of tiles with consistently spaced x/y positions. Provided data is plotted as a marker at each position unless None. This can be used to create all sorts of visualizations that have spatially related data, such as calendars
View Source
"""Coordinate chart.
Creates a grid of tiles with consistently spaced x/y positions. Provided data is plotted as a marker at each position
unless None. This can be used to create all sorts of visualizations that have spatially related data, such as calendars
"""
import calendar
import cmath
import math
from itertools import chain
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from .utils_fig import CustomChart, check_raw_data
# PLANNED: subplots for multiple years of calendar charts (Subplot title is year)
def calculate_grid(grid_dims, corners, width, height):
"""Calculate the grid x and y coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
corners: dictionary with keys `(x, y)` containing lists of the four exterior corner coordinates
width: float width in pixels
height: float height in pixels
Returns:
dict: with keys `(x, y)` with lists of lists containing float values
"""
grid = {'x': [], 'y': []}
for r_idx in range(grid_dims[0]):
y_offset = height * (grid_dims[0] - r_idx)
y_grid = [y_offset - _y for _y in corners['y']]
for c_idx in range(grid_dims[1]):
x_offset = width * c_idx
grid['x'].extend([x_offset + _x for _x in corners['x']])
grid['y'].extend(y_grid)
return grid
def calculate_border(grid_dims, width, height):
"""Calculate each line in all borders.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
width: float width in pixels
height: float height in pixels
Returns:
list: containing dictionaries keys `(x, y)` and values for the two points for each line in grid
"""
return [
{
'x': [c_idx * width] * 2,
'y': [0, height * grid_dims[0]],
} for c_idx in range(grid_dims[1] + 1)
] + [
{
'x': [0, width * grid_dims[1]],
'y': [r_idx * height] * 2,
} for r_idx in range(grid_dims[0] + 1)
]
class CoordinateChart(CustomChart): # noqa: H601
"""Coordinate Chart."""
border_opacity: float = 0.2
"""Border opacity for grid. Value must be in [0-1] where 0 is none. Default is 0.2."""
border_line = None
"""Dictionary passed to plotly `line`. Used to set thickness, color, dash style, etc. Default is None."""
marker_kwargs = None
"""Marker keyword arguments used in `create_marker()`. Default is None."""
# Private states for managing coordinate chart dimensions
_grid: dict
_borders: list
def __init__(self, *, title, grid_dims, corners, titles=None, layout_overrides=()):
"""Initialize Coordinate Chart and store parameters as data members.
Args:
title: String title for chart (can be an empty string for blank)
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
corners: dictionary with keys `(x, y)` containing lists of the four corner coordinates
titles: list of strings that will appear in each tile. Default is None for no titles
layout_overrides: Custom parameters in format [ParentKey, SubKey, Value] to customize 'go.layout'
"""
# Initialize base method. Sets xlabel and ylabel to empty strings because coordinate chart use the x/y
# axis for arranging points. Data is displayed by color or size
super().__init__(title=title, xlabel='', ylabel='', layout_overrides=layout_overrides)
# Initialize chart parameters
self.calculate_layout(grid_dims, corners, titles)
def calculate_layout(self, grid_dims, corners, titles):
"""Calculate coordinate chart layout. Called by __init__, but can be called later to update the chart.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
corners: dictionary with keys `(x, y)` containing lists of the four corner coordinates
titles: list of strings that will appear in each tile. Default is None for no titles
"""
# Calculate exterior height and width of grid
width = float(np.max(corners['x']) + np.min(corners['x']))
height = float(np.max(corners['y']) + np.min(corners['y']))
# Set grid and border coordinates for traces
self._grid = calculate_grid(grid_dims, corners, width, height)
self._borders = calculate_border(grid_dims, width, height)
# Add titles to annotations if provided
if titles is None:
self.annotations = []
else:
v_offset = np.min(corners['y']) * 0.4
self.annotations = [
go.layout.Annotation(
ax=0, ay=0,
x=(idx % grid_dims[1] + 0.5) * width, # noqa: S001
y=(grid_dims[0] - int(idx / grid_dims[1]) % grid_dims[0]) * height - v_offset,
text=title,
)
for idx, title in enumerate(titles) if title is not None
]
def create_traces(self, df_raw):
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with at minimum the column `values: str`
Returns:
list: Dash chart traces
"""
# Check that the raw data frame is properly formatted
check_raw_data(df_raw, min_keys=['values'])
# Merge x/y grid data with values. Temporarily extend values with None, then drop those rows
values = df_raw['values'].to_list()
values.extend([None] * (len(self._grid['x']) - len(values)))
df_grid = pd.DataFrame(
data={
'values': values,
'x': self._grid['x'],
'y': self._grid['y'],
},
).dropna()
return [
go.Scatter(
hoverinfo='none',
line=self.border_line or {'color': 'black'},
mode='lines',
opacity=self.border_opacity,
showlegend=False,
x=border['x'],
y=border['y'],
) for border in self._borders
] + [
go.Scatter(
hoverinfo='text',
mode='markers',
showlegend=False,
text=df_grid['values'],
x=df_grid['x'],
y=df_grid['y'],
marker=self.create_marker(df_grid, **(self.marker_kwargs or {})),
),
]
def create_marker(self, df_grid, colorscale='Viridis', size=16, symbol='circle'):
"""Return a dictionary for the scatter plot.
See: https://plot.ly/python/colorscales/ (Named colorscales: Reds, Bluered, Jet, Viridis, Cividis, etc.)
Args:
df_grid: pandas dataframe with at minimum the column `values: str`, `x: float`, `y: float`
colorscale: plotly colorscale, see doc link above. Default is 'Viridis'
size: integer marker size
symbol: marker symbol (square, circle, circle-open, x, etc.)
Returns:
dict: the chart marker shape, symbol, color, etc.
"""
return {
'color': df_grid['values'],
'colorscale': colorscale,
'showscale': True,
'size': size,
'symbol': symbol,
}
def create_layout(self):
"""Extend the standard layout.
Returns:
dict: layout for Dash figure
"""
layout = super().create_layout()
for axis in ['xaxis', 'yaxis']:
layout[axis]['showgrid'] = False
layout[axis]['showticklabels'] = False
layout[axis]['zeroline'] = False
layout['yaxis']['scaleanchor'] = 'x'
layout['yaxis']['scaleratio'] = 1
return layout
# ==============================================================================
# Standard Coordinate Grids
class GridClass:
"""Base class for specifying a grid for the Coordinate chart."""
marker_kwargs = None
"""Marker keyword arguments. Default is None."""
def __init__(self, grid_dims, titles):
"""Initialize the coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
titles: list of titles to place in each grid element
"""
self.grid_dims = grid_dims
self.titles = titles
class CircleGrid(GridClass): # noqa: H601
"""Grid of circular coordinates."""
marker_kwargs = {'size': 10}
"""Marker keyword arguments. Default is `{'size': 10}`"""
def __init__(self, grid_dims, titles=None):
"""Initialize the coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
titles: list of titles to place in each grid element. Default is None
"""
if titles is None:
titles = []
for idx in range(grid_dims[0] * grid_dims[1]):
x_coord = int(idx / grid_dims[1]) + 1
y_coord = idx % (grid_dims[0] + 1) + 1 # noqa: S001
titles.append(f'Subtitle for ({x_coord}, {y_coord})')
super().__init__(grid_dims=grid_dims, titles=titles)
# Calculate four corners
opp = 0.5 * math.cos(cmath.pi / 4)
adj = 0.5 * math.sin(cmath.pi / 4)
self.corners = {
'x': [0.5, 1 - adj, 1.0, 1 + adj, 1.5, 1 + adj, 1.0, 1 - adj],
'y': [1.0, 1 - opp, 0.5, 1 - opp, 1.0, 1 + opp, 1.5, 1 + opp],
}
def calculate_calendar_grid_corners(margin, days_in_week=7, max_weeks_in_month=6):
"""Calculate the four exterior corner coordinates of a calendar coordinate grid.
Args:
margin: float spacing between tiles
days_in_week: number of days in week. Default is 7
max_weeks_in_month: max number of weeks in a month. Default is 6
Returns:
list: dictionary with keys `(x, y)` containing lists of the four exterior corner coordinates
"""
y_indices = [[idx] * days_in_week for idx in range(max_weeks_in_month)]
return {
'x': np.add([*range(days_in_week)] * max_weeks_in_month, margin),
'y': np.add([*chain.from_iterable(y_indices)], margin),
}
class YearGrid(GridClass): # noqa: H601
"""Coordinates of days within a grid of months in one year."""
marker_kwargs = {'size': 10, 'symbol': 'square'}
"""Marker keyword arguments. Default is `{'size': 10, 'symbol': 'square'}`"""
def __init__(self, grid_dims=(4, 3), titles=None):
"""Initialize the coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`. Default is (4, 3)
titles: list of titles to place in each grid element. Default is None
Raises:
RuntimeError: if error in the grid dimensions
"""
if grid_dims[0] * grid_dims[1] != 12: # pragma: no cover
raise RuntimeError('Calendar must show all 12 months Expected (12,1), (6,2), (4,3), (1,12), etc.')
if titles is None:
titles = calendar.month_name[1:]
super().__init__(grid_dims=grid_dims, titles=titles)
# Calculate four corners
self.corners = calculate_calendar_grid_corners(margin=2)
def format_data(self, month_lists, year):
"""Return the formatted list that can be passed to a coordinate chart.
Args:
month_lists: list of daily values where each sublist is one month starting with January
year: year expressed in 4 decimal places (i.e. 2019)
Returns:
list: of values with additional None values to align with grid
"""
values = []
for idx_month, daily_list in enumerate(month_lists):
idx_first_day, count_days = calendar.monthrange(year, idx_month + 1)
idx_first_day += 1 # Increment to start on Sunday -- PLANNED: make this configureable
values.extend([None] * idx_first_day)
values.extend(daily_list)
values.extend([None] * (len(self.corners['x']) - idx_first_day - count_days))
return values
class MonthGrid(GridClass): # noqa: H601
"""Coordinates of days within a single month."""
marker_kwargs = {'size': 35, 'symbol': 'square'}
"""Marker keyword arguments. Default is `{'size': 35, 'symbol': 'square'}`"""
def __init__(self, grid_dims=(1, 1), titles=None):
"""Initialize the coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`. Default is (1, 1)
titles: list of titles to place in each grid element. Default is None
Raises:
RuntimeError: if error in the grid dimensions or titles
"""
if grid_dims != (1, 1): # pragma: no cover
raise RuntimeError('Day grid can only show one month, expected (1, 1)')
if titles is not None and len(titles) != 1: # pragma: no cover
raise RuntimeError(f'Only one title is allowed for the MonthGrid. Received: {titles}')
super().__init__(grid_dims=grid_dims, titles=titles)
# Calculate four corners
self.corners = calculate_calendar_grid_corners(margin=1.25)
def format_data(self, daily_values, year, month):
"""Return the formatted list that can be passed to a coordinate chart.
Args:
daily_values: list of values for each day of month
year: year expressed in 4 digits (2019, 2020, etc.)
month: month index in [1, 12]
Returns:
list: of values with additional None values to align with grid
"""
idx_first_day = calendar.monthrange(year, month)[0]
values = [None] * idx_first_day
values.extend(daily_values)
return values
Functions⚓︎
calculate_border⚓︎
def calculate_border(
grid_dims,
width,
height
)
Calculate each line in all borders.
Parameters:
| Name | Description |
|---|---|
| grid_dims | tuple of the number of tiles in grid. In format (row, column) |
| width | float width in pixels |
| height | float height in pixels |
Returns:
| Type | Description |
|---|---|
| list | containing dictionaries keys (x, y) and values for the two points for each line in grid |
View Source
def calculate_border(grid_dims, width, height):
"""Calculate each line in all borders.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
width: float width in pixels
height: float height in pixels
Returns:
list: containing dictionaries keys `(x, y)` and values for the two points for each line in grid
"""
return [
{
'x': [c_idx * width] * 2,
'y': [0, height * grid_dims[0]],
} for c_idx in range(grid_dims[1] + 1)
] + [
{
'x': [0, width * grid_dims[1]],
'y': [r_idx * height] * 2,
} for r_idx in range(grid_dims[0] + 1)
]
calculate_calendar_grid_corners⚓︎
def calculate_calendar_grid_corners(
margin,
days_in_week=7,
max_weeks_in_month=6
)
Calculate the four exterior corner coordinates of a calendar coordinate grid.
Parameters:
| Name | Description |
|---|---|
| margin | float spacing between tiles |
| days_in_week | number of days in week. Default is 7 |
| max_weeks_in_month | max number of weeks in a month. Default is 6 |
Returns:
| Type | Description |
|---|---|
| list | dictionary with keys (x, y) containing lists of the four exterior corner coordinates |
View Source
def calculate_calendar_grid_corners(margin, days_in_week=7, max_weeks_in_month=6):
"""Calculate the four exterior corner coordinates of a calendar coordinate grid.
Args:
margin: float spacing between tiles
days_in_week: number of days in week. Default is 7
max_weeks_in_month: max number of weeks in a month. Default is 6
Returns:
list: dictionary with keys `(x, y)` containing lists of the four exterior corner coordinates
"""
y_indices = [[idx] * days_in_week for idx in range(max_weeks_in_month)]
return {
'x': np.add([*range(days_in_week)] * max_weeks_in_month, margin),
'y': np.add([*chain.from_iterable(y_indices)], margin),
}
calculate_grid⚓︎
def calculate_grid(
grid_dims,
corners,
width,
height
)
Calculate the grid x and y coordinates.
Parameters:
| Name | Description |
|---|---|
| grid_dims | tuple of the number of tiles in grid. In format (row, column) |
| corners | dictionary with keys (x, y) containing lists of the four exterior corner coordinates |
| width | float width in pixels |
| height | float height in pixels |
Returns:
| Type | Description |
|---|---|
| dict | with keys (x, y) with lists of lists containing float values |
View Source
def calculate_grid(grid_dims, corners, width, height):
"""Calculate the grid x and y coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
corners: dictionary with keys `(x, y)` containing lists of the four exterior corner coordinates
width: float width in pixels
height: float height in pixels
Returns:
dict: with keys `(x, y)` with lists of lists containing float values
"""
grid = {'x': [], 'y': []}
for r_idx in range(grid_dims[0]):
y_offset = height * (grid_dims[0] - r_idx)
y_grid = [y_offset - _y for _y in corners['y']]
for c_idx in range(grid_dims[1]):
x_offset = width * c_idx
grid['x'].extend([x_offset + _x for _x in corners['x']])
grid['y'].extend(y_grid)
return grid
Classes⚓︎
CircleGrid⚓︎
class CircleGrid(
grid_dims,
titles=None
)
View Source
class CircleGrid(GridClass): # noqa: H601
"""Grid of circular coordinates."""
marker_kwargs = {'size': 10}
"""Marker keyword arguments. Default is `{'size': 10}`"""
def __init__(self, grid_dims, titles=None):
"""Initialize the coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
titles: list of titles to place in each grid element. Default is None
"""
if titles is None:
titles = []
for idx in range(grid_dims[0] * grid_dims[1]):
x_coord = int(idx / grid_dims[1]) + 1
y_coord = idx % (grid_dims[0] + 1) + 1 # noqa: S001
titles.append(f'Subtitle for ({x_coord}, {y_coord})')
super().__init__(grid_dims=grid_dims, titles=titles)
# Calculate four corners
opp = 0.5 * math.cos(cmath.pi / 4)
adj = 0.5 * math.sin(cmath.pi / 4)
self.corners = {
'x': [0.5, 1 - adj, 1.0, 1 + adj, 1.5, 1 + adj, 1.0, 1 - adj],
'y': [1.0, 1 - opp, 0.5, 1 - opp, 1.0, 1 + opp, 1.5, 1 + opp],
}
Ancestors (in MRO)⚓︎
- dash_charts.coordinate_chart.GridClass
Class variables⚓︎
marker_kwargs
Marker keyword arguments. Default is {'size': 10}
CoordinateChart⚓︎
class CoordinateChart(
*,
title,
grid_dims,
corners,
titles=None,
layout_overrides=()
)
View Source
class CoordinateChart(CustomChart): # noqa: H601
"""Coordinate Chart."""
border_opacity: float = 0.2
"""Border opacity for grid. Value must be in [0-1] where 0 is none. Default is 0.2."""
border_line = None
"""Dictionary passed to plotly `line`. Used to set thickness, color, dash style, etc. Default is None."""
marker_kwargs = None
"""Marker keyword arguments used in `create_marker()`. Default is None."""
# Private states for managing coordinate chart dimensions
_grid: dict
_borders: list
def __init__(self, *, title, grid_dims, corners, titles=None, layout_overrides=()):
"""Initialize Coordinate Chart and store parameters as data members.
Args:
title: String title for chart (can be an empty string for blank)
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
corners: dictionary with keys `(x, y)` containing lists of the four corner coordinates
titles: list of strings that will appear in each tile. Default is None for no titles
layout_overrides: Custom parameters in format [ParentKey, SubKey, Value] to customize 'go.layout'
"""
# Initialize base method. Sets xlabel and ylabel to empty strings because coordinate chart use the x/y
# axis for arranging points. Data is displayed by color or size
super().__init__(title=title, xlabel='', ylabel='', layout_overrides=layout_overrides)
# Initialize chart parameters
self.calculate_layout(grid_dims, corners, titles)
def calculate_layout(self, grid_dims, corners, titles):
"""Calculate coordinate chart layout. Called by __init__, but can be called later to update the chart.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
corners: dictionary with keys `(x, y)` containing lists of the four corner coordinates
titles: list of strings that will appear in each tile. Default is None for no titles
"""
# Calculate exterior height and width of grid
width = float(np.max(corners['x']) + np.min(corners['x']))
height = float(np.max(corners['y']) + np.min(corners['y']))
# Set grid and border coordinates for traces
self._grid = calculate_grid(grid_dims, corners, width, height)
self._borders = calculate_border(grid_dims, width, height)
# Add titles to annotations if provided
if titles is None:
self.annotations = []
else:
v_offset = np.min(corners['y']) * 0.4
self.annotations = [
go.layout.Annotation(
ax=0, ay=0,
x=(idx % grid_dims[1] + 0.5) * width, # noqa: S001
y=(grid_dims[0] - int(idx / grid_dims[1]) % grid_dims[0]) * height - v_offset,
text=title,
)
for idx, title in enumerate(titles) if title is not None
]
def create_traces(self, df_raw):
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with at minimum the column `values: str`
Returns:
list: Dash chart traces
"""
# Check that the raw data frame is properly formatted
check_raw_data(df_raw, min_keys=['values'])
# Merge x/y grid data with values. Temporarily extend values with None, then drop those rows
values = df_raw['values'].to_list()
values.extend([None] * (len(self._grid['x']) - len(values)))
df_grid = pd.DataFrame(
data={
'values': values,
'x': self._grid['x'],
'y': self._grid['y'],
},
).dropna()
return [
go.Scatter(
hoverinfo='none',
line=self.border_line or {'color': 'black'},
mode='lines',
opacity=self.border_opacity,
showlegend=False,
x=border['x'],
y=border['y'],
) for border in self._borders
] + [
go.Scatter(
hoverinfo='text',
mode='markers',
showlegend=False,
text=df_grid['values'],
x=df_grid['x'],
y=df_grid['y'],
marker=self.create_marker(df_grid, **(self.marker_kwargs or {})),
),
]
def create_marker(self, df_grid, colorscale='Viridis', size=16, symbol='circle'):
"""Return a dictionary for the scatter plot.
See: https://plot.ly/python/colorscales/ (Named colorscales: Reds, Bluered, Jet, Viridis, Cividis, etc.)
Args:
df_grid: pandas dataframe with at minimum the column `values: str`, `x: float`, `y: float`
colorscale: plotly colorscale, see doc link above. Default is 'Viridis'
size: integer marker size
symbol: marker symbol (square, circle, circle-open, x, etc.)
Returns:
dict: the chart marker shape, symbol, color, etc.
"""
return {
'color': df_grid['values'],
'colorscale': colorscale,
'showscale': True,
'size': size,
'symbol': symbol,
}
def create_layout(self):
"""Extend the standard layout.
Returns:
dict: layout for Dash figure
"""
layout = super().create_layout()
for axis in ['xaxis', 'yaxis']:
layout[axis]['showgrid'] = False
layout[axis]['showticklabels'] = False
layout[axis]['zeroline'] = False
layout['yaxis']['scaleanchor'] = 'x'
layout['yaxis']['scaleratio'] = 1
return layout
Ancestors (in MRO)⚓︎
- dash_charts.utils_fig.CustomChart
Class variables⚓︎
annotations
border_line
Dictionary passed to plotly line. Used to set thickness, color, dash style, etc. Default is None.
border_opacity
marker_kwargs
Marker keyword arguments used in create_marker(). Default is None.
Instance variables⚓︎
axis_range
Specify x/y axis range or leave as empty dictionary for autorange.
Methods⚓︎
apply_custom_layout⚓︎
def apply_custom_layout(
self,
layout
)
Extend and/or override layout with custom settings.
Parameters:
| Name | Description |
|---|---|
| layout | base layout dictionary. Typically from self.create_layout() |
Returns:
| Type | Description |
|---|---|
| dict | layout for Dash figure |
View Source
def apply_custom_layout(self, layout):
"""Extend and/or override layout with custom settings.
Args:
layout: base layout dictionary. Typically from self.create_layout()
Returns:
dict: layout for Dash figure
"""
for parent_key, sub_key, value in self.layout_overrides:
if sub_key is not None:
layout[parent_key][sub_key] = value
else:
layout[parent_key] = value
return layout
calculate_layout⚓︎
def calculate_layout(
self,
grid_dims,
corners,
titles
)
Calculate coordinate chart layout. Called by init, but can be called later to update the chart.
Parameters:
| Name | Description |
|---|---|
| grid_dims | tuple of the number of tiles in grid. In format (row, column) |
| corners | dictionary with keys (x, y) containing lists of the four corner coordinates |
| titles | list of strings that will appear in each tile. Default is None for no titles |
View Source
def calculate_layout(self, grid_dims, corners, titles):
"""Calculate coordinate chart layout. Called by __init__, but can be called later to update the chart.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
corners: dictionary with keys `(x, y)` containing lists of the four corner coordinates
titles: list of strings that will appear in each tile. Default is None for no titles
"""
# Calculate exterior height and width of grid
width = float(np.max(corners['x']) + np.min(corners['x']))
height = float(np.max(corners['y']) + np.min(corners['y']))
# Set grid and border coordinates for traces
self._grid = calculate_grid(grid_dims, corners, width, height)
self._borders = calculate_border(grid_dims, width, height)
# Add titles to annotations if provided
if titles is None:
self.annotations = []
else:
v_offset = np.min(corners['y']) * 0.4
self.annotations = [
go.layout.Annotation(
ax=0, ay=0,
x=(idx % grid_dims[1] + 0.5) * width, # noqa: S001
y=(grid_dims[0] - int(idx / grid_dims[1]) % grid_dims[0]) * height - v_offset,
text=title,
)
for idx, title in enumerate(titles) if title is not None
]
create_figure⚓︎
def create_figure(
self,
df_raw,
**kwargs_data
)
Create the figure dictionary.
Parameters:
| Name | Description |
|---|---|
| df_raw | data to pass to formatter method |
| kwargs_data | keyword arguments to pass to the data formatter method |
Returns:
| Type | Description |
|---|---|
| dict | keys data and layout for Dash |
View Source
def create_figure(self, df_raw, **kwargs_data):
"""Create the figure dictionary.
Args:
df_raw: data to pass to formatter method
kwargs_data: keyword arguments to pass to the data formatter method
Returns:
dict: keys `data` and `layout` for Dash
"""
return {
'data': self.create_traces(df_raw, **kwargs_data),
'layout': go.Layout(self.apply_custom_layout(self.create_layout())),
}
create_layout⚓︎
def create_layout(
self
)
Extend the standard layout.
Returns:
| Type | Description |
|---|---|
| dict | layout for Dash figure |
View Source
def create_layout(self):
"""Extend the standard layout.
Returns:
dict: layout for Dash figure
"""
layout = super().create_layout()
for axis in ['xaxis', 'yaxis']:
layout[axis]['showgrid'] = False
layout[axis]['showticklabels'] = False
layout[axis]['zeroline'] = False
layout['yaxis']['scaleanchor'] = 'x'
layout['yaxis']['scaleratio'] = 1
return layout
create_marker⚓︎
def create_marker(
self,
df_grid,
colorscale='Viridis',
size=16,
symbol='circle'
)
Return a dictionary for the scatter plot.
See: https://plot.ly/python/colorscales/ (Named colorscales: Reds, Bluered, Jet, Viridis, Cividis, etc.)
Parameters:
| Name | Description |
|---|---|
| df_grid | pandas dataframe with at minimum the column values: str, x: float, y: float |
| colorscale | plotly colorscale, see doc link above. Default is ‘Viridis’ |
| size | integer marker size |
| symbol | marker symbol (square, circle, circle-open, x, etc.) |
Returns:
| Type | Description |
|---|---|
| dict | the chart marker shape, symbol, color, etc. |
View Source
def create_marker(self, df_grid, colorscale='Viridis', size=16, symbol='circle'):
"""Return a dictionary for the scatter plot.
See: https://plot.ly/python/colorscales/ (Named colorscales: Reds, Bluered, Jet, Viridis, Cividis, etc.)
Args:
df_grid: pandas dataframe with at minimum the column `values: str`, `x: float`, `y: float`
colorscale: plotly colorscale, see doc link above. Default is 'Viridis'
size: integer marker size
symbol: marker symbol (square, circle, circle-open, x, etc.)
Returns:
dict: the chart marker shape, symbol, color, etc.
"""
return {
'color': df_grid['values'],
'colorscale': colorscale,
'showscale': True,
'size': size,
'symbol': symbol,
}
create_traces⚓︎
def create_traces(
self,
df_raw
)
Return traces for plotly chart.
Parameters:
| Name | Description |
|---|---|
| df_raw | pandas dataframe with at minimum the column values: str |
Returns:
| Type | Description |
|---|---|
| list | Dash chart traces |
View Source
def create_traces(self, df_raw):
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with at minimum the column `values: str`
Returns:
list: Dash chart traces
"""
# Check that the raw data frame is properly formatted
check_raw_data(df_raw, min_keys=['values'])
# Merge x/y grid data with values. Temporarily extend values with None, then drop those rows
values = df_raw['values'].to_list()
values.extend([None] * (len(self._grid['x']) - len(values)))
df_grid = pd.DataFrame(
data={
'values': values,
'x': self._grid['x'],
'y': self._grid['y'],
},
).dropna()
return [
go.Scatter(
hoverinfo='none',
line=self.border_line or {'color': 'black'},
mode='lines',
opacity=self.border_opacity,
showlegend=False,
x=border['x'],
y=border['y'],
) for border in self._borders
] + [
go.Scatter(
hoverinfo='text',
mode='markers',
showlegend=False,
text=df_grid['values'],
x=df_grid['x'],
y=df_grid['y'],
marker=self.create_marker(df_grid, **(self.marker_kwargs or {})),
),
]
initialize_mutables⚓︎
def initialize_mutables(
self
)
Initialize the mutable data members to prevent modifying one attribute and impacting all instances.
View Source
def initialize_mutables(self):
"""Initialize the mutable data members to prevent modifying one attribute and impacting all instances."""
...
GridClass⚓︎
class GridClass(
grid_dims,
titles
)
View Source
class GridClass:
"""Base class for specifying a grid for the Coordinate chart."""
marker_kwargs = None
"""Marker keyword arguments. Default is None."""
def __init__(self, grid_dims, titles):
"""Initialize the coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`
titles: list of titles to place in each grid element
"""
self.grid_dims = grid_dims
self.titles = titles
Descendants⚓︎
- dash_charts.coordinate_chart.CircleGrid
- dash_charts.coordinate_chart.YearGrid
- dash_charts.coordinate_chart.MonthGrid
Class variables⚓︎
marker_kwargs
Marker keyword arguments. Default is None.
MonthGrid⚓︎
class MonthGrid(
grid_dims=(1, 1),
titles=None
)
View Source
class MonthGrid(GridClass): # noqa: H601
"""Coordinates of days within a single month."""
marker_kwargs = {'size': 35, 'symbol': 'square'}
"""Marker keyword arguments. Default is `{'size': 35, 'symbol': 'square'}`"""
def __init__(self, grid_dims=(1, 1), titles=None):
"""Initialize the coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`. Default is (1, 1)
titles: list of titles to place in each grid element. Default is None
Raises:
RuntimeError: if error in the grid dimensions or titles
"""
if grid_dims != (1, 1): # pragma: no cover
raise RuntimeError('Day grid can only show one month, expected (1, 1)')
if titles is not None and len(titles) != 1: # pragma: no cover
raise RuntimeError(f'Only one title is allowed for the MonthGrid. Received: {titles}')
super().__init__(grid_dims=grid_dims, titles=titles)
# Calculate four corners
self.corners = calculate_calendar_grid_corners(margin=1.25)
def format_data(self, daily_values, year, month):
"""Return the formatted list that can be passed to a coordinate chart.
Args:
daily_values: list of values for each day of month
year: year expressed in 4 digits (2019, 2020, etc.)
month: month index in [1, 12]
Returns:
list: of values with additional None values to align with grid
"""
idx_first_day = calendar.monthrange(year, month)[0]
values = [None] * idx_first_day
values.extend(daily_values)
return values
Ancestors (in MRO)⚓︎
- dash_charts.coordinate_chart.GridClass
Class variables⚓︎
marker_kwargs
Marker keyword arguments. Default is {'size': 35, 'symbol': 'square'}
Methods⚓︎
format_data⚓︎
def format_data(
self,
daily_values,
year,
month
)
Return the formatted list that can be passed to a coordinate chart.
Parameters:
| Name | Description |
|---|---|
| daily_values | list of values for each day of month |
| year | year expressed in 4 digits (2019, 2020, etc.) |
| month | month index in [1, 12] |
Returns:
| Type | Description |
|---|---|
| list | of values with additional None values to align with grid |
View Source
def format_data(self, daily_values, year, month):
"""Return the formatted list that can be passed to a coordinate chart.
Args:
daily_values: list of values for each day of month
year: year expressed in 4 digits (2019, 2020, etc.)
month: month index in [1, 12]
Returns:
list: of values with additional None values to align with grid
"""
idx_first_day = calendar.monthrange(year, month)[0]
values = [None] * idx_first_day
values.extend(daily_values)
return values
YearGrid⚓︎
class YearGrid(
grid_dims=(4, 3),
titles=None
)
View Source
class YearGrid(GridClass): # noqa: H601
"""Coordinates of days within a grid of months in one year."""
marker_kwargs = {'size': 10, 'symbol': 'square'}
"""Marker keyword arguments. Default is `{'size': 10, 'symbol': 'square'}`"""
def __init__(self, grid_dims=(4, 3), titles=None):
"""Initialize the coordinates.
Args:
grid_dims: tuple of the number of tiles in grid. In format `(row, column)`. Default is (4, 3)
titles: list of titles to place in each grid element. Default is None
Raises:
RuntimeError: if error in the grid dimensions
"""
if grid_dims[0] * grid_dims[1] != 12: # pragma: no cover
raise RuntimeError('Calendar must show all 12 months Expected (12,1), (6,2), (4,3), (1,12), etc.')
if titles is None:
titles = calendar.month_name[1:]
super().__init__(grid_dims=grid_dims, titles=titles)
# Calculate four corners
self.corners = calculate_calendar_grid_corners(margin=2)
def format_data(self, month_lists, year):
"""Return the formatted list that can be passed to a coordinate chart.
Args:
month_lists: list of daily values where each sublist is one month starting with January
year: year expressed in 4 decimal places (i.e. 2019)
Returns:
list: of values with additional None values to align with grid
"""
values = []
for idx_month, daily_list in enumerate(month_lists):
idx_first_day, count_days = calendar.monthrange(year, idx_month + 1)
idx_first_day += 1 # Increment to start on Sunday -- PLANNED: make this configureable
values.extend([None] * idx_first_day)
values.extend(daily_list)
values.extend([None] * (len(self.corners['x']) - idx_first_day - count_days))
return values
Ancestors (in MRO)⚓︎
- dash_charts.coordinate_chart.GridClass
Class variables⚓︎
marker_kwargs
Marker keyword arguments. Default is {'size': 10, 'symbol': 'square'}
Methods⚓︎
format_data⚓︎
def format_data(
self,
month_lists,
year
)
Return the formatted list that can be passed to a coordinate chart.
Parameters:
| Name | Description |
|---|---|
| month_lists | list of daily values where each sublist is one month starting with January |
| year | year expressed in 4 decimal places (i.e. 2019) |
Returns:
| Type | Description |
|---|---|
| list | of values with additional None values to align with grid |
View Source
def format_data(self, month_lists, year):
"""Return the formatted list that can be passed to a coordinate chart.
Args:
month_lists: list of daily values where each sublist is one month starting with January
year: year expressed in 4 decimal places (i.e. 2019)
Returns:
list: of values with additional None values to align with grid
"""
values = []
for idx_month, daily_list in enumerate(month_lists):
idx_first_day, count_days = calendar.monthrange(year, idx_month + 1)
idx_first_day += 1 # Increment to start on Sunday -- PLANNED: make this configureable
values.extend([None] * idx_first_day)
values.extend(daily_list)
values.extend([None] * (len(self.corners['x']) - idx_first_day - count_days))
return values
Created: August 5, 2022